                    EJECT
*******************************************************************************
*
myNewValue          start
*
* Description:      Called when the value of the control is to be changed.
*                   The area under the control is invalidated so that it will
*                   be redrawn.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:
*                   Import Snap2Grid
*                   Import myDrawCtl
*                   Import SetCtlRect
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    PushLong #myCtlRect ; erase old control
                    _EraseRect

                    PushLong #myCtlRect ; invalidate area erased
                    _InvalRect

                    ldx #^myCtlRect     ; make sure control is on the grid
                    lda #myCtlRect
                    jsr Snap2Grid

                    jsr SetCtlRect

                    lda #0              ; return no value
                    tax

                    rts
                    end

                    EJECT
*******************************************************************************
*
mySetParams         start
*
* Description:      Set new parameters command. Don't set if either of them
*                   are negative.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    lda theParam        ; these have gotta go on in reverse
                    sta <work+2
                    lda theParam+2
                    sta <work

                    ora <work+2         ; is this a NULL pointer?
                    bne CopyIt          ; no - so use it

                    lda #^StdCtlData    ; yes - so set pointer to default data
                    sta <work+2
                    lda #StdCtlData
                    sta <work

CopyIt              ANOP
                    ldy #oCtlSize-2     ; copy the data into control record.
                    ldx #oCtlSize-oCtlMinY-2
loop                phy
                    txy
                    lda [<work],y
                    ply
                    sta [<CtlPtr],y
                    dey
                    dey
                    dex
                    dex
                    bpl loop

                    lda #0              ; return no value
                    tax

                    rts
                    end

                    EJECT
*******************************************************************************
*
myRecSize           start
*
* Description:      Return record size command.
*
*
* Inputs:           NONE
*
* Outputs:          A = Size of the control record to allocate.
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    lda #oCtlSize       ; low word of record size
                    ldx #0              ; high word

                    rts
                    end

                    EJECT
*******************************************************************************
*
Null                start
*
* Description:      Null routine. Does nothing. Returns no error.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************

                    lda #0
                    tax

                    rts
                    end

                    EJECT
*******************************************************************************
*
InitCtlData         start
*
* Description:      Initialize some data that will needs to be handy: Get a
*                   pointer to the control record (we are supplied only with
*                   a handle), make a local copy of the control rectangle, set
*                   up a pointer to a color table, and move CtlParam from a
*                   Direct Page location into an absolute location.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

;
; Check to see if the Control Manager is sending us a 'RecSize' call. If it
; is, then we cannot rely on the validity of the Control handle, as it has not
; yet been allocated and is essentially garbage. Since we do not know what it
; points to, we don't want to read from it, as it may actually accidentally
; point to something dangerous, like the IWM I/O locations!!! So if we detect
; a RecSize code, skip over this routine.
;
                    lda <CtlCode        ; is this a RecSize call?
                    cmp #recSize
                    beq done            ; yes - skip this whole routine

;
; Copy the value in CtlParam into a local location -- off of the direct page.
; We do this for convenience. We may need to pass a pointer to CtlParam to
; a toolbox routine, but you can't do that for a direct page location. The
; number assiciated with a direct page location is really an offset off of the
; base specified by the Direct Page register; it is NOT an absolute memory
; location. So that we can conveniently use 'PushLong #location' if we need
; to, I copy CtlParam to a non-direct page location.
;
                    lda <CtlParam
                    sta theParam
                    lda <CtlParam+2
                    sta theParam+2

                    ldy #2              ; get a handy pointer to the CtlRec
                    lda [<theCtlHandle],y
                    sta <CtlPtr+2
                    lda [<theCtlHandle]
                    sta <CtlPtr

                    ldy #oCtlRect+6     ; copy the control rect over to the
                    ldx #6              ; local variable.
loop                lda [<CtlPtr],y
                    sta myCtlRect,x
                    dey
                    dey
                    dex
                    dex
                    bpl loop

;
; The following values are used by Snap2Grid. However, Snap2Grid can be
; called with an invalid Direct Page, so we copy them out the control
; record (which is pointed to by a pointer in our Direct Page) and into
; some absolute locations that we can get to at all times.
;
                    ldy #oCtlGridX      ; get the X and Y spacings out of the
                    lda [<CtlPtr],y     ; control record and into some local
                    sta myCtlGridX      ; storage for easy handling.

                    ldy #oCtlGridY
                    lda [<CtlPtr],y
                    sta myCtlGridY

; Check the color table pointer. If it is non-zero, install it as the
; pointer to the color table we'll be using. If not, then install a
; pointer to a default color table.

                    ldy  #octlcolor     ; Get the pointer to the color
                    lda [<CtlPtr],y     ; table in the control record.
                    sta ColorPtr        ; Save it to a local pointer.
                    iny
                    iny
                    lda [<CtlPtr],y
                    sta ColorPtr+2
                    ora ColorPtr        ; is it a NULL pointer?
                    bne done            ; no, so use it.

                    lda #StdColorTable  ; yes, install my own color table.
                    sta ColorPtr
                    lda #^StdColorTable
                    sta ColorPtr+2

done                ANOP
                    rts

                    end

                    EJECT
*******************************************************************************
*
SetCtlRect          start
*
* Description:      This routine is called when the control's bounding
*                   rectangle has been changed and needs to be written back
*                   out to the control record.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    ldy #oCtlRect+6     ; get an offset into the control rec.
                    ldx #6              ; index to my local record.
loop                ANOP
                    lda myCtlRect,x
                    sta [<CtlPtr],y
                    dey
                    dey
                    dex
                    dex
                    bpl loop

                    rts

                    end

                    EJECT
*******************************************************************************
*
SetMyPen            start
*
* Description:      We want our control to look good in both 320 and 640 mode.
*                   Because vertical lines are thinner in 640 than 320, its a
*                   good idea to draw them doubly thick just so they can be
*                   seen. This routine will determine what mode we are in and
*                   adjust the pen accordingly.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    pha                 ; we base pen width on the MasterSCB
                    _GetMasterSCB
                    pla
                    and #$0080          ; check the mode bit
                    beq pen320          ; we are in 320 mode - go push a 1

                    PushWord #2         ; in 640 mode - push on a 2 width
                    bra setpn

pen320              PushWord #1         ; in 320 mode - push on a 1 width

setpn               ANOP                ; width is 1 or 2
                    PushWord #1         ; Height is always 1
                    _SetPenSize

                    rts
                    end

                    EJECT
*******************************************************************************
*
CalcCorners         start
*
* Description:      This routine calculates the rectangles for all of the
*                   little knobs that are used to grow the control. They are
*                   stored locally - not with the control record - and so need
*                   to be calculated every time we need to check for hits or
*                   to draw the control.
*
*
* Inputs:           NONE
*
* Outputs:          knobXXX, FrameHeight, FrameWidth variables are initialized.
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

top                 equ 0               ; alias these to something a little
left                equ 2               ; more descriptive.
bottom              equ 4
right               equ 6

; Get the size of the knobs. The height and width are stored in the CtlValue
; field in a packed format. This unpacks them and stores them locally for
; ease of access.

                    ldy #oCtlValue
                    lda [<CtlPtr],y
                    and #$00FF
                    sta FrameHeight
                    lda [<CtlPtr],y
                    xba
                    and #$00FF
                    sta FrameWidth

;
; We "assembly line" the initialization of the knob rectangles. Instead
; of calculating just one knob at a time, we do a few of them all at the
; same time. Here we initialize the tops and bottoms of the 3 top knobs.

                    lda myCtlRect+top
                    sta knobUL+top
                    sta knobTop+top
                    sta knobUR+top
                    clc
                    adc FrameHeight
                    sta knobUL+bottom
                    sta knobTop+bottom
                    sta knobUR+bottom

;
; Initialize the lefts and rights of the three left knobs.
;
                    lda myCtlRect+left
                    sta knobUL+left
                    sta knobLeft+left
                    sta knobLL+left
                    clc
                    adc FrameWidth
                    sta knobUL+right
                    sta knobLeft+right
                    sta knobLL+right

;
; Initialize the tops and bottoms of the three bottom knobs.
;
                    lda myCtlRect+bottom
                    sta knobLL+bottom
                    sta knobBottom+bottom
                    sta knobLR+bottom
                    sec
                    sbc FrameHeight
                    sta knobLL+top
                    sta knobBottom+top
                    sta knobLR+top

;
; Initialize the lefts and rights of the three right knobs.
;
                    lda myCtlRect+right
                    sta knobUR+right
                    sta knobRight+right
                    sta knobLR+right
                    sec
                    sbc FrameWidth
                    sta knobUR+left
                    sta knobRight+left
                    sta knobLR+left

;
; At this point, the 4 corner knobs have been completely initialized.
; If we are going to be using the edge knobs, then we'll have to do
; some extra math. However, if we aren't, then we'll skip over the
; math.

                    ldy #oCtlFlag
                    lda [<CtlPtr],y
                    and #knobEdgeMask
                    beq done

; Do extra setup for edge knobs
;
; First, get top and bottom coordinates for the side edges:
;
;     top = (myCtlRect.top + myCtlRect.bottom - FrameHeight)/2
;     bot = (myCtlRect.top + myCtlRect.bottom + FrameHeight)/2

                    clc
                    lda myCtlRect+top
                    adc myCtlRect+bottom
                    pha
                    adc FrameHeight
                    lsr a
                    sta knobLeft+bottom
                    sta knobRight+bottom
                    pla
                    sec
                    sbc FrameHeight
                    lsr a
                    sta knobLeft+top
                    sta knobRight+top

;
; Now perform similar calculations for the left and right sides
; of the top and bottom edge knobs.
;
                    clc
                    lda myCtlRect+left
                    adc myCtlRect+right
                    pha
                    adc FrameWidth
                    lsr a
                    sta knobTop+right
                    sta knobBottom+right
                    pla
                    sec
                    sbc FrameWidth
                    lsr a
                    sta knobTop+left
                    sta knobBottom+left

done                ANOP
                    rts
                    end

                    EJECT
*******************************************************************************
*
Snap2Grid           start
*
* Description:      This control defproc lets the application optionally create
*                   an invisible grid based on local coordinates. When this is
*                   done, the 4 corners of the control are always aligned to
*                   the coordinates of this grid. This occurs when we drag or
*                   resize the control, or call MoveControl. Snap2Grip is the
*                   routine that ensures we are on the grid.
*
*
* Inputs:           A = low word of pointer to rectangle to fix
*                   X = high word
*
* Outputs:          rectangle is modified in place.
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

oldD                equ 1
oldB                equ oldD+2
rectPtr             equ oldB+1

;
; We are going to use a local direct page here. This is because Snap2Grid is
; called by my routine that draws the control's recangle as it is being
; dragged. When that happens, we are not using our normal direct page, and
; hence can't use any of the values or work locations there. So we set up
; another one with it's own workspace.
;
                    phx                 ; save the pointer to the rectangle
                    pha

                    phb                 ; save the caller's data bank register
                    phd                 ; save the caller'd direct page reg.

                    phk                 ; switch data bank to program bank
                    plb

                    tsc                 ; switch direct page to stack pointer
                    tcd


;
; WhchGridYX is used to help generalize this routine. We support different
; spacings in the X and Y directions of the grid. As we loop through all 4
; coordinates of the rectangle, we want to make sure that we are using the
; correct spacing. 'whichGridYX' holds flags that tell us when to use X and
; when to use Y spacing (0 = use Y spacing; 1 = use X spacing).
;
                    lda #%0101          ; Set up some indices into GridYX
                    sta whichGridYX

;
; Fix the coordinates by using the following method:
;
;  1. Get the difference between the coordinate we are examining and
;     the next lowest grid location:
;
;          z = coordinate mod GridSize
;
;  2. Z tells how far away from the next lowest grid line
;     the coordinate we are looking at is, so subtracting that
;     value will put the coordinate on the grid.
;
;          coordinate := coordinate - z
;
;  3. However, this is simply truncating, and I don't like the
;     way that feels when resizing the control. So lets add an
;     instruction so that it ROUNDS instead.
;
;          if z > GridSize/2 then
;               coordinate := coordinate + GridSize
;

                    ldy #6              ; init our loop index
Loop                ANOP
                    sty YSave

                    lda whichGridYX     ; do we now use X or Y grid spacing?
                    and #%0001
                    beq UseY
                    lda myCtlGridX
                    bra UseIt
UseY                ANOP
                    lda myCtlGridY

; we now have the gridsize in the Acc
UseIt               ANOP
                    cmp #2              ; Is it 0 or 1? If so, then skip over
                    blt ShortCircuit    ;   dividing routine.
                    sta GridSize        ; save this for the divide later.

                    pha                 ; push space on stack for the result
                    pha                 ; of the divide we are going to do.

                    lda [<rectPtr],y    ; push on the coordinate we are
                    pha                 ;   adjusting as the Dividend
                    PushWord GridSize   ; push this on as the divisor
                    _UDivide            ; unsigned divide
                    pla                 ; quotient - we don't want it.
                    PullWord Remainder  ; remainder is 'z' - Keep it!

                    ldy YSave           ; get my coordinate index back.
                    lda [<rectPtr],y    ; get the coordinate to change
                    sec
                    sbc Remainder       ; change it
                    sta [<rectPtr],y    ; save it back out

                    lda GridSize        ; Make a GridSize/2
                    lsr a
                    cmp Remainder       ; is remainder < GridSize/2?
                    bge ShortCircuit    ; yes - so we're done
                    lda [<rectPtr],y    ; no - so bump the coordinate
                    clc
                    adc GridSize
                    sta [<rectPtr],y

ShortCircuit        lsr whichGridYX     ; move the next index into place
                    dey                 ; bump down to the next coordinate
                    dey                 ;   of the rectangle.
                    bpl Loop

Exit                ANOP
                    pld                 ; restore caller's Direct Page reg.
                    plb                 ; restore data bank register
                    pla                 ; remove rect pointer
                    pla
                    rts                 ; all done - go bye-bye

whichGridYX         ds 2
YSave               ds 2
Remainder           ds 2
GridSize            ds 2

                    end

                    EJECT
*******************************************************************************
*
FindPart            start
*
* Description:      This routine is called to find out exactly which part of
*                   the control we clicked in. It first checks to see if we
*                   clicked in any of the grow knobs. If so, then it returns
*                   an 'internal partcode' telling the calling routine which
*                   knob was clicked in. If a knob wasn't clicked in, a check
*                   is made to see if we clicked on the frame. If we are
*                   allowed to click in the interior, a check is made to see
*                   if we clicked there, too.
*
*
* Inputs:           NONE
*
* Outputs:          A = internal partcode of part hit (1-10)
*
* External Refs:
*                   Import CalcCorners
*
* Entry Points:
*                   entry TestFrame     ; called by myTestCtl
*
*******************************************************************************
                    using CtlData

                    jsr CalcCorners

;
; We first check to see if the mouse was clicked in any of the knobs. We do
; this by assuming a knob was clicked in, and then checking to see if that was
; true. If so, we leave here with the partcode we assumed. If not, we loop
; through and check all of the other knobs. If none of the knobs was clicked
; in, we see if the frame was clicked on.

                    ldx #LeftPart       ; get last knob's part code
                    ldy #7*8            ; point to last knob's rect

loop                ANOP
                    phx                 ; save partcode we're checking for...
                    phy                 ; and the rect's pointer

                    pha                 ; space for _PtInRect result
                    PushLong #theParam  ; push on the point to check
                    lda #^knobUL        ; create a pointer to the rect
                    pha
                    clc                 ; Bumping just the low byte is OK
                    tya                 ;   unless the GS suddenly lets
                    adc #knobUL         ;   code segments cross bank
                    pha                 ;   boundaries
                    _PtInRect
                    pla                 ; get result of PtInRect
                    ply                 ; retrieve rectangle index
                    plx                 ; retrieve part code
                    cmp #0              ; did we score a hit?
                    bne InKnob          ; yes, return with the part code
                    dex                 ; move down to next partcode
                    sec                 ; move down to next rectangle
                    tya
                    sbc #8
                    tay
                    bpl loop            ; keep going until rect index < 0

                    bra TestFrame       ; not in knobs - try frame

;
; We just scored a hit on one of the knobs, and X holds the knob number.
; However, the application may not have corner or edge knobs active. So
; if we clicked on an inactive knob, have to check for that and return
; 'inFrame' instead of a knob partcode.
;
InKnob              ANOP
                    ldy #oCtlFlag       ; are corner knobs active?
                    lda [<CtlPtr],y
                    and #knobCornerMask
                    bne ckEdge          ; yes - so pass this code on.

                    txa                 ; Corners not allowed. Did we hit one?
                    lsr a
                    bcs retFramePart    ; yes, so return click on Frame instead

ckEdge              ANOP
                    ldy #oCtlFlag       ; are edge knobs active?
                    lda [<CtlPtr],y
                    and #knobEdgeMask
                    bne done            ; yes - so return this code

                    txa                 ; Edges not allowed. Did we hit one?
                    lsr a
                    bcs TestFrame       ; no, so pass this code on.
RetFramePart        ANOP
                    ldx #FramePart      ; yes, so return click on Frame instead
done                ANOP
                    txa                 ; put the part code in the Acc
                    rts

TestFrame           entry

; First test to see if it is within the Control's rectangle. It should
; be, but let's check anyway...

                    pha
                    PushLong #theParam
                    PushLong #myCtlRect
                    _PtInRect
                    pla
                    beq NotInFrame      ; no in the frame. Signal noPart

; Now see if we differentiate between the frame and the interior

                    ldy #oCtlFlag
                    lda [<CtlPtr],y
                    and #dragIntMask
                    bne RetFramePart    ; nope - everything drags

; Yes we do. Now see if we hit the frame fair and square. Inset the
; control's bounding rectangle by the size of the knobs that are on
; it to create an inner rectangle. If we fall between those two
; rectangles, then I say that you have landed on the frame. If we
; got this far, then we know that we are within the outer rectangle.
; Now see if we are outside of the inner rectangle.

                    ldx #6              ; make a copy of the control rect
CopyLoop            lda myCtlRect,x     ; so that we can reduce it.
                    sta innerRect,x
                    dex
                    dex
                    bpl CopyLoop

                    PushLong #innerRect ; push on pointer to the rect to inset
                    PushWord FrameWidth ; push on the amounts to inset it by
                    PushWord FrameHeight ; (these vals inited by CalcCorners)
                    _InsetRect

                    pha                 ; see if we are outside of the inner
                    PushLong #theParam  ; rectangle
                    PushLong #innerRect
                    _PtInRect

                    pla                 ; check the result
                    beq RetFramePart    ; we were between rects!

NotInFrame          lda #noPart         ; Not *exactly* on the frame.
                    rts

innerRect           ds 8

                    end
